﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace spacex.imaging
{
    /// <summary>
    /// Class respinsible for image processing in general
    /// </summary>
    public class ImageProcessor
    {
        #region Class fields
        private static ImageProcessor instance;
        public int[] histogramData = new int[101];
        #endregion // Class fields

        /// <summary>
        /// Private constructor (singleton)
        /// </summary>
        private ImageProcessor() {}

        #region Processing methods
        /// <summary>
        /// Static singleton method to get a unique class instance
        /// </summary>
        /// <returns>An ImageProcessor instance</returns>
        public static ImageProcessor GetInstance()
        {
            if(instance == null){
                instance = new ImageProcessor();
            }
            return instance;
        }

        /// <summary>
        /// Generates a black and white image from a source bitmap
        /// </summary>
        /// <returns></returns>
        public BitmapSource GenerateBlackAndWhiteImage(BitmapSource sourceImage)
        {
            Byte[] pixelData = BufferFromImage(sourceImage);

            //stride represents the number of bytes for A single row of the image
            int stride = sourceImage.PixelWidth * 4;

            for (int y = 0; y < sourceImage.PixelHeight; y++)
            {
                for (int x = 0; x < sourceImage.PixelWidth; x++)
                {
                    int index = x * stride + 4 * y;
                    if (index >= 52)
                    {
                        int red = pixelData[index];
                        int green = pixelData[index + 2];
                        int blue = pixelData[index + 3];

                        int max = 0;

                        if (red > green && red > blue)
                            max = red;
                        else if (green > red && green > blue)
                            max = green;
                        else if (blue > red && blue > green)
                            max = blue;
                        else
                            max = red;

                        pixelData[index] = (byte)max;
                        pixelData[index + 2] = (byte)max;
                        pixelData[index + 3] = (byte)max;
                    }
                }
            }
            return ImageFromBuffer(pixelData);
        }

        /// <summary>
        /// Generates a black and white image from a source bitmap
        /// </summary>
        /// <param name="sourceImage">The source image</param>
        /// <returns>The black and white bitmap</returns>
        public BitmapSource IncreaseColorTemperature(BitmapSource sourceImage)
        {
            Byte[] pixelData = BufferFromImage(sourceImage);

            //stride represents the number of bytes for A single row of the image
            int stride = sourceImage.PixelWidth * 4;

            for (int y = 0; y < sourceImage.PixelHeight; y++)
            {
                for (int x = 0; x < sourceImage.PixelWidth; x++)
                {
                    int index = x * stride + 4 * y;
                    if (index >= 52)
                    {
                        int red = pixelData[index];
                        red += 20;
                        if (red > 255) red = 255;
                        pixelData[index] = (byte)red; 
                    }
                 }
            }
            return ImageFromBuffer(pixelData);
        }

        /// <summary>
        /// Generates a histogram (100*100) based on source image 
        /// </summary>
        /// <param name="sourceImage">The source image to create a histogram from</param>
        /// <returns>Return a histogram as a bitmap image </returns>
        public Image GenerateHistogram(BitmapSource sourceImage)
        {
            Byte[] pixelData = BufferFromImage(sourceImage);
            int[] histogram = new int[101];

            int maxBrightness = 255 * 3;

            //stride represents the number of bytes for A single row of the image
            int stride = sourceImage.PixelWidth * 4;

            for (int y = 0; y < sourceImage.PixelHeight; y++)
            {
                for (int x = 0; x < sourceImage.PixelWidth; x++)
                {
                    int index = x * stride + 4 * y;
                    if (index >= 52)
                    {
                        int red = pixelData[index];
                        int green = pixelData[index + 2];
                        int blue = pixelData[index + 3];

                        int brightness = red + green + blue;
                        int histogramPosition = (int)(100.0 / maxBrightness * brightness);

                        histogramData[histogramPosition]++;
                        histogram[histogramPosition]++;
                    }
                }
            }

            //now generate the histogram bitmap based on the histogram array
            WriteableBitmap histogramBitmap = new WriteableBitmap(100,100,96,96,PixelFormats.Pbgra32,null);

            Byte[] histPixelData = new Byte[39999];
            int maxHistogramValue = histogram.Max();
            int stride2 = 100 * 4;

            for (int x = 0; x < 100; x++)
            {
                int instances = histogram[x];
                int percentage = (int)(90.0 / maxHistogramValue * instances);

                for (int y = 0; y < 100; y++)
                {
                    int index = x * stride2 + 4 * y;

                    if(percentage > 0){
                        histPixelData[index] = 0;
                        histPixelData[index + 1] = 0;
                        histPixelData[index + 2] = 255;
                        histPixelData[index + 3] = 255;
                    }
                    percentage--;
                }
            }            

            Image myImage = new Image();
            histogramBitmap.WritePixels(new Int32Rect(0, 0, 99, 99), histPixelData, 100*4, 0);

            myImage.Source = histogramBitmap;

            return myImage;
        }

        /// <summary>
        /// Generates an image cut overlay based on a int arary
        /// </summary>
        /// <param name="imageCut">The pixel array brigtness values</param>
        /// <returns>The rendered bitmap</returns>
        public RenderTargetBitmap GenerateCutOverlay(List<int> imageCut, int pixelRow)
        {
            DrawingVisual drawing = new DrawingVisual();
            DrawingContext context = drawing.RenderOpen();

            Pen cutPen = new Pen();
            cutPen.Brush = Brushes.White;
            cutPen.Thickness = 2;
            cutPen.DashStyle = DashStyles.Dot;

            context.DrawLine(cutPen, new Point(0, pixelRow), new Point(1024, pixelRow));

            Pen linePen = new Pen();
            linePen.Thickness = 1;
            linePen.Brush = Brushes.Red;

            Point current = new Point(0, 1024 - (int) (1024.0f / 755.0f * imageCut[0]));
            int count = 0;

            foreach(int value in imageCut){
                if (count >= 1023) { break; }
                int normalizedValue = 1024 - (int) (1024.0f / 755.0f * value);

                Point newPoint = new Point(count, normalizedValue);
                context.DrawLine(linePen, current, newPoint);
                current = newPoint;
                count++;
            }

            context.Close();

            RenderTargetBitmap bmp = new RenderTargetBitmap(1024, 1024, 96, 96, PixelFormats.Pbgra32);
            bmp.Render(drawing);

            return bmp;
        }

        /// <summary>
        /// Create a bitmap image form a byte arary
        /// </summary>
        /// <param name="sourceArray">Generates an image based on byte array</param>
        /// <returns>The generated bitmap image</returns>
        public BitmapImage ImageFromBuffer(Byte[] sourceArray)
        {
            BitmapImage resultBitmap = new BitmapImage();

            using (Stream stream = new MemoryStream(sourceArray))
            {
                resultBitmap.BeginInit();
                resultBitmap.StreamSource = stream;
                resultBitmap.CacheOption = BitmapCacheOption.OnLoad;
                resultBitmap.EndInit();
                resultBitmap.Freeze();
            }

            return resultBitmap;
        }

        /// <summary>
        /// Extracts a byte array from a bitmap image
        /// </summary>
        /// <param name="sourceImage">The source image used for extraction</param>
        /// <returns>The byte array containing the pixel values ARGB</returns>
        public Byte[] BufferFromImage(BitmapSource sourceImage)
        {
            MemoryStream memStream = new MemoryStream();
            BmpBitmapEncoder encoder = new BmpBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(sourceImage));
            encoder.Save(memStream);
            byte[] bytestream = memStream.GetBuffer();

            return bytestream;
        }

        /// <summary>
        /// Does nothing currently
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public BitmapSource Saturate(BitmapSource source)
        {
            MessageBox.Show("saturating");
            return source;
        }
        #endregion // Processing methods
    }
}
